package com.gitee.wsl.ext.array

import com.gitee.wsl.ext.base.BYTE_BITS
import com.gitee.wsl.ext.base.BYTE_MASK
import com.gitee.wsl.ext.bitcode.toBinaryString
import com.gitee.wsl.ext.collection.fill
import com.gitee.wsl.ext.collection.getByIndex
import com.gitee.wsl.ext.string.EMPTY
import com.gitee.wsl.io.buffer.ByteBuffer
import com.gitee.wsl.io.buffer.buffered
import com.gitee.wsl.io.buffer.readContent
import com.gitee.wsl.lang.ByteOrder
import kotlin.math.ceil
import kotlin.math.floor


//fun ByteArray.toByteString(offset: Int = 0, byteCount: Int = this.size) = ByteString(this, offset, byteCount)

fun ByteArray.toBinaryString(padding: Boolean = true) =
    joinToString("") {
        (it.toInt() and BYTE_MASK).toString(2).run {
            if (padding) padStart(BYTE_BITS, '0') else this
        }
    }

/** Returns a binary string representation of this [UByteArray] value. */
@OptIn(ExperimentalUnsignedTypes::class)
fun UByteArray.toBinaryString(): String = joinToString(String.EMPTY) { it.toBinaryString() }

/**
 * Returns all bits as they are accessible by the global bit index (i.e. after wrapping this ByteArray into a BitSet)
 *
 * Note that this representation conflicts with the usual binary representation of a byte array for the following reason:
 *
 * Printing a byte array usually shows the MS*Byte* at the right-most position, but each byte's MS*Bit*
 * at a byte's individual left-most position, leading to bit and byte indices running in opposing directions.
 *
 * The string representation returned by this function can simply be interpreted as a list of boolean values
 * accessible by a monotonic index running in one direction.
 *
 * See the following illustration of memory layout vs. bit string index and the resulting string:
 * ```
 * ┌──────────────────────────────┐
 * │                              │
 * │                              │ Addr: 2
 * │      0  0  0  0  1  1  0  1       │
 * │   ◄─23─22─21─20─19─18─17─16─┐  │
 * │                           │  │
 * ├─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ┤
 * │                           │  │
 * │ ┌─────────────────────────┘  │ Addr: 1
 * │ │  1  0  0  0  1  0  0  0    │
 * │ └─15─14─12─12─11─10──9──8─┐  │
 * │                           │  │
 * ├─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ┤
 * │                           │  │
 * │ ┌─────────────────────────┘  │ Addr: 0
 * │ │  1  0  1  1  0  1  1  1    │
 * │ └──7──6──5──4──3──2──1──0──────index─◄─
 * │                              │
 * └──────────────────────────────┘
 *```
 *
 * This leads to the following bit string:
 * 11101101000100011011
 */
fun ByteArray.toBitString(): String =
    joinToString(separator = "") {
        it.toUByte().toString(2).padStart(8, '0').reversed()
    }.dropLastWhile { it == '0' }


fun ByteArray.toAsciiString(hasSpace: Boolean = false) =
    this.map { it.toInt().toChar() }.joinToString(if (hasSpace) " " else "")



fun ByteArray.bytesToBits(): BooleanArray {
    val bitArray = BooleanArray(size * 8)

    for(i in indices) {
        val byte = this[i].toUByte().toInt() //Preserve leftmost bit
        bitArray[(8 * i)] = (byte and 0b1) == 1
        bitArray[(8 * i) + 1] = ((byte shr 1) and 0b1) == 1
        bitArray[(8 * i) + 2] = ((byte shr 2) and 0b1) == 1
        bitArray[(8 * i) + 3] = ((byte shr 3) and 0b1) == 1
        bitArray[(8 * i) + 4] = ((byte shr 4) and 0b1) == 1
        bitArray[(8 * i) + 5] = ((byte shr 5) and 0b1) == 1
        bitArray[(8 * i) + 6] = ((byte shr 6) and 0b1) == 1
        bitArray[(8 * i) + 7] = ((byte shr 7) and 0b1) == 1
    }

    return bitArray
}

fun BooleanArray.bitsToBytes(): ByteArray {
    val bits = this
    val byteArray = ByteArray(size shr 3)

    for(i in byteArray.indices) {
        byteArray[i] = (bits[(i * 8)].int or
                (bits[(i * 8) + 1].int shl 1) or
                (bits[(i * 8) + 2].int shl 2) or
                (bits[(i * 8) + 3].int shl 3) or
                (bits[(i * 8) + 4].int shl 4) or
                (bits[(i * 8) + 5].int shl 5) or
                (bits[(i * 8) + 6].int shl 6) or
                (bits[(i * 8) + 7].int shl 7)).toByte()
    }
    return byteArray
}

internal val Boolean.int
    get() = this.compareTo(false)



/**
 * Converts a Uint8Array into a Uint8Array variant, in which each element
 * encodes 5 bits of the original byte array.
 *
 * @param {Uint8Array} src
 *   Input to convert
 * @param {?Uint8Array} dst
 *   Optional output buffer. If specified, the sequence of 5-bit chunks will be written there
 *   if not specified, the output buffer will be created from scratch. The length
 *   of `outBuffer` is not checked.
 * @returns {Uint8Array}
 *   Output buffer consisting of 5-bit chunks
 *
 * @api public
 */
fun ByteArray.to5BitArray(dst: ByteArray? = null): ByteArray {
    val len = ceil((size.toDouble() * 8.0) / 5.0).toInt()
    val realDst = dst ?: ByteArray(len) { 0 }

    return toBits(this, 5, realDst)
}

fun ByteArray.from5BitArray(dst: ByteArray? = null): ByteArray {
    val len = floor((size.toDouble() * 5.0) / 8.0).toInt()
    val realDst = dst ?: ByteArray(len) { 0 }

    return fromBits(this, 5, realDst)
}

/**
 * Encodes a `ByteArray` buffer as an array with a lesser number of bits per element.
 *
 * @api private
 */
internal fun toBits(
    src: ByteArray,
    bits: Int,
    dst: ByteArray,
): ByteArray {
    require((bits > 8) || (bits < 1)){"Invalid bits per element 1 to 8 expected"}
//    if ((bits > 8) || (bits < 1)) {
//        throw RangeError("Invalid bits per element 1 to 8 expected")
//    }
    // `BitArray<8>` is equivalent to `Uint8Array` unfortunately, Flow
    // has problems expressing this, so the explicit conversion is performed here.
    convert(src, 8, dst, bits, true)
    return dst
}

internal fun fromBits(
    src: ByteArray,
    bits: Int,
    dst: ByteArray,
): ByteArray {
    require((bits > 8) || (bits < 1)){"Invalid bits per element 1 to 8 expected"}
//    if ((bits > 8) || (bits < 1)) {
//        throw IllegalArgumentException("Invalid bits per element 1 to 8 expected")
//    }

    convert(src, bits, dst, 8, false)
    return dst
}

/**
 * Converts an array from one number of bits per element to another.
 *
 * @api private
 */
internal fun convert(
    src: ByteArray,
    srcBits: Int,
    dst: ByteArray,
    dstBits: Int,
    pad: Boolean,
) {
    val mask: Int = (1 shl dstBits) - 1
    var acc = 0
    var bits = 0
    var pos = 0
    src.forEach { b ->
        // Pull next bits from the input buffer into accumulator.
        // when converting b to int only take first byte (matters for negatives)
        acc = (acc shl srcBits) or (b.toInt() and 0xff)
        bits += srcBits

        // Push into the output buffer while there are enough bits in the accumulator.
        while (bits >= dstBits) {
            bits -= dstBits
            dst[pos] = ((acc shr bits) and mask).toByte()
            pos += 1
        }
    }

    if (pad) {
        if (bits > 0) {
            // `dstBits - rem.bits` is the number of trailing zero bits needed to be appended
            // to accumulator bits to get the trailing bit group.
            dst[pos] = ((acc shl (dstBits - bits)) and mask).toByte()
        }
    } else {
        // Truncate the remaining padding, but make sure that it is zeroed and not
        // overly long first.
        if (bits >= srcBits) {
            throw Error("Excessive padding: ${bits} (max ${srcBits - 1} allowed)")
        }
        if (acc % (1 shl bits) != 0) {
            throw Error("Non-zero padding")
        }
    }
}

fun Collection<Byte>.toByteArray() = ByteArray(this.size){this.getByIndex(it) }
